// copyright 1993 Michael B. Johnson; some portions copyright 1994, MIT
// see COPYRIGHT for reuse legalities
//

#import "WW3DShader.h"
#import <3Dkit/3Dkit.h>

#import "WWShaderArgPointMatrix.h"
#import "WWEveParser.h"

@implementation WW3DShader

+ initialize { return [WW3DShader setVersion:4], self; }

static lightHandleSeed = 0;

static BOOL
validQuickShader(const char *name)
{
  if (!name)
  {  return NO;
  }
  if (!strcmp("plastic", name))
  {  return YES;
  }
  if (!strcmp("paintedplastic", name))
  {  return YES;
  }
  if (!strcmp("constant", name))
  {  return YES;
  }
  if (!strcmp("matte", name))
  {  return YES;
  }
  if (!strcmp("metal", name))
  {  return YES;
  }
  if (!strcmp("shinymetal", name))
  {  return YES;
  }
  if (!strcmp("ambientlight", name))
  {  return YES;
  }
  if (!strcmp("distantlight", name))
  {  return YES;
  }
  if (!strcmp("pointlight", name))
  {  return YES;
  }
  if (!strcmp("spotlight", name))
  {  return YES;
  }
  if (!strcmp("depthcue", name))
  {  return YES;
  }
  if (!strcmp("fog", name))
  {  return YES;
  }
  if (!strcmp("bumpy", name))
  {  return YES;
  }
  return NO;
}

- init
{
  if (![super init])
  {  return nil;
  }

  // for each of the tokens, we need to see if it corresponds to a
  //  valid argument of this shader.  If it does, we set the value 
  // using the corresponding parm.
  n = 0;
  tokens = NULL;
  parms = NULL;
  archiveVector = NULL;
  printfNVector = NULL;
  printfTypeVector = NULL;
  iconImage = [NXImage findImageNamed:"shader"];
  [self assignQuickShader];
  usingSupportedQRManShader = NO;

  lightHandle = 0;

  // dis ain't dat portable, ya know...
  ribColor[0] = 1.0;
  ribColor[1] = 1.0;
  ribColor[2] = 1.0;

  return self;
}


- (BOOL)pushesOrPopsCTM { return NO; }
- (BOOL)pushesCTM { return NO; }
- (BOOL)popsCTM { return NO; }

- initWithShader:(const char *)shaderName n:(int)newN tokens:(RtToken *)newTokens parms:(RtPointer *)newParms archiveVector:(char **)newArchiveVector printfTypeVector:(int *)newPrintfTypeVector printfNVector:(int *)newPrintfNVector
{
  int  i;


  if (![super initWithShader:shaderName])
  {  return nil;
  }

  [self setShader:shaderName];
  [self setUseColor:NO];

  // for each of the tokens, we need to see if it corresponds to a
  //  valid argument of this shader.  If it does, we set the value 
  // using the corresponding parm.
  n = newN;
  tokens = newTokens;
  parms = newParms;
  archiveVector = newArchiveVector;
  printfNVector = (int *)malloc(n * sizeof(int));
  printfTypeVector = (int *)malloc(n * sizeof(int));

  for (i = 0; i < n; i++)
  {  if (![self setShaderArgNamed:tokens[i] withValue:parms[i]])
     {  NXLogError("in shader %s, <%s> is not a valid arg.\n", 
		   shaderName, tokens[i]);
     }
     printfNVector[i] = newPrintfNVector[i];
     printfTypeVector[i] = newPrintfTypeVector[i];
  }

  lightHandle = 0;

  // dis ain't dat portable, ya know...
  ribColor[0] = 1.0;
  ribColor[1] = 1.0;
  ribColor[2] = 1.0;

  return self;
}

- setShader:(const char *)name
{
  if (![super setShader:name])
  {  return nil;
  }
  if (name)
  {  iconImage = [NXImage findImageNamed:(char *)name];
  }
  else
  {  iconImage = nil;
  }
  if (!iconImage)
  {  iconImage = [NXImage findImageNamed:"shader"];
  }
  if (validQuickShader(name))
  {  quickShader = NXCopyStringBuffer(name);
     usingSupportedQRManShader = YES;
  }
  else
  {  [self assignQuickShader];
     usingSupportedQRManShader = NO;
  }

  return self;
}

- assignQuickShader
{
  if (quickShader) {  free(quickShader); }
  switch ([self shaderType])
  {  
     case SLO_TYPE_SURFACE:
       // really should be arbitrarily smart here
       // you can look at the parameters of the shaders, etc. to try and figure out which it use
       // for now, just just use plastic.
       quickShader = NXCopyStringBuffer("plastic");
       break;

     case SLO_TYPE_LIGHT:
       quickShader = NXCopyStringBuffer("pointlight");
       break;

     case SLO_TYPE_DISPLACEMENT:
       quickShader = NXCopyStringBuffer("bumpy");
       break;

     case SLO_TYPE_VOLUME:
       // need to figure out what kind...
       quickShader = NXCopyStringBuffer("depthcue");
       break;

     case SLO_TYPE_TRANSFORMATION:
       quickShader = NULL;
       break;

     case SLO_TYPE_IMAGER:
       quickShader = NULL;
       break;

     default:
       quickShader = NULL;
       return nil;
  }

  return self;
}

- setShaderArgNamed:(const char *)argName withValue:(RtPointer)value
{
  SLO_TYPE   argType;


  argType = [self shaderArgType:argName];
  if (argType == SLO_TYPE_POINT)
  {  return [self setShaderArg:argName pointValue:*((RtPoint *)value)];
  }
  if (argType == SLO_TYPE_COLOR)
  {  // note that it wants an NXColor, not an RtColor...
     RtFloat r = *((RtFloat *)value); 
     RtFloat g = *((RtFloat *)(value+sizeof(RtFloat))); 
     RtFloat b = *((RtFloat *)(value+(2*sizeof(RtFloat))));
     NXColor  anNXColor = NXConvertRGBToColor((float)r, (float)g, (float)b);
     
     return [self setShaderArg:argName colorValue:anNXColor];
  }
  if (argType == SLO_TYPE_SCALAR)
  {  return [self setShaderArg:argName floatValue:(*((RtFloat *)value))];
  }
  if (argType == SLO_TYPE_STRING)
  {  return [self setShaderArg:argName stringValue:(*(char **)value)];
  }

  return nil;
}


- (const char *)quickShader {  return quickShader; }
- setQuickShader:(const char *)newQuickShader 
{  
   if (!validQuickShader(newQuickShader))
   {  NXLogError("%s is not a shader supported by qrman, yabba-head!\n");
      return nil;
   }
   if (quickShader)
   {  free(quickShader);
   }
   quickShader = NXCopyStringBuffer(newQuickShader);
   return self;
}

- setColor:(NXColor)aColor
{
  ribColor[0] = NXRedComponent(aColor);
  ribColor[1] = NXGreenComponent(aColor);
  ribColor[2] = NXBlueComponent(aColor);
  return [super setColor:aColor];
}

- (RtToken)token { return token; }

- setToken:(RtToken)newToken
{
  token = newToken;
  return self;
}

- set
{
  if (NXDrawingStatus == NX_DRAWING)
  {  
     if ([self doesUseColor])
     {  RiColor(ribColor);
     }
     if (quickShader)
     {  switch ([self shaderType])
        {  
           case SLO_TYPE_SURFACE:
             RiSurfaceV(quickShader, n, tokens, parms);
             break;

          case SLO_TYPE_LIGHT:
            if (!token)
            {  RiCreateHandle(token, RI_LIGHTSOURCE);
 	    }
            if (pointOrArea)
            {  lightHandle = RiAreaLightSourceV(token, quickShader, n, tokens, parms);
            }
            else
            {  lightHandle = RiLightSourceV(token, quickShader, n, tokens, parms);
            }
            break;

          default:
            break;
        }
     }
  }
  else
  {  
     [super set];
  }

  return self;
}

// WWRenderable

- (BOOL)hasBoundingBox  { return NO; }
- calculateBoundingBoxStartingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime  { return self; }

- renderMaps:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime usingStream:(NXStream *)ns
{
  return self;
}
- renderMaps:(WW3DCamera *)camera usingStream:(NXStream *)ns
{
  return self;
}

- renderMaps:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime 
{
  return self;
}
- renderMaps:(WW3DCamera *)camera
{
  return self;
}

- setAtmosphere { volumeType = WW_ATMOSPHERE; return self; }
- setInterior { volumeType = WW_INTERIOR; return self; }
- setExterior { volumeType = WW_EXTERIOR; return self; }
- setPointSource { pointOrArea = 0; return self; }
- setAreaSource { pointOrArea = 1; return self; }

- renderSelfAsBox:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime 
{ return [self renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
} 

- renderSelf:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime 
{
  switch ([self shaderType])
  {  
     case SLO_TYPE_SURFACE:
       RiSurfaceV((char *)[self shader], n, tokens, parms);
       break;

     case SLO_TYPE_LIGHT:
       if (!token) {  RiCreateHandle(token, RI_LIGHTSOURCE); } 
       if (pointOrArea)
       {  lightHandle = RiAreaLightSourceV(token, (char *)[self shader], n, tokens, parms);
       }
       else
       {  lightHandle = RiLightSourceV(token, (char *)[self shader], n, tokens, parms);
       }
       break;

     case SLO_TYPE_DISPLACEMENT:
       RiDisplacementV((char *)[self shader], n, tokens, parms);
       break;

     case SLO_TYPE_VOLUME:
       switch (volumeType)
       {  case WW_ATMOSPHERE:  RiAtmosphereV((char *)[self shader], n, tokens, parms);
                               break;
          case WW_INTERIOR:    RiInteriorV((char *)[self shader], n, tokens, parms);
                               break;
          case WW_EXTERIOR:    RiExteriorV((char *)[self shader], n, tokens, parms);
                               break;
          default:             NXLogError("unknown volume shader %s!\n", (char *)[self shader]);
       }
       break;

     case SLO_TYPE_TRANSFORMATION:
       NXLogError("warning: using RiDeformationV for Transformation shader %s...\n", [self shader]);
       RiDeformationV((char *)[self shader], n, tokens, parms);
       break;

     case SLO_TYPE_IMAGER:
       RiImagerV((char *)[self shader], n, tokens, parms);
       break;

     default:
       return nil;
  }
  return self;
}

- renderSelf:(WW3DCamera *)camera
{
  RtFloat  shutterOpenTime = [camera shutterOpenTime],
           shutterCloseTime = [camera shutterCloseTime];


  return [self renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
}

// this is a tough one; should the shader prerender itself?  For some shaders, especially ones
// that make holes in objects, the answer is probably yes, but for those that are merely complicated
// texturers, they shouldn't.  Probably best to keep a flag around to decide...
// for now, just render
- preRenderSelf:(WW3DCamera *)camera startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime 
{
  return [self renderSelf:camera startingAt:shutterOpenTime endingAt:shutterCloseTime];
}

- preRenderSelf:(WW3DCamera *)camera 
{
  return [self renderSelf:camera];
}

- transformCTM:(WW3DAttributeState *)attributeState startingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime { return self; }

- setBoundingBox:(RtBound *)newBoundingBox { return nil; }
- (RtBound *)boundingBoxStartingAt:(RtFloat)shutterOpenTime endingAt:(RtFloat)shutterCloseTime { return (RtBound *)NULL; };

- (void)setN:(int)newN tokens:(RtToken *)newTokens parms:(RtPointer *)newParms archiveVector:(char **)newArchiveVector printfTypeVector:(int *)newPrintfTypeVector printfNVector:(int *)newPrintfNVector
{  
   int  i;

 
   if (n)
   {  for (i = 0; i < n; i++)
      {  // don't free the tokens; they're shared!
         free(parms[i]);
         free(archiveVector[i]);
         free(printfNVector);
         free(printfTypeVector);
      }
      if (newN > n)
      {  tokens = (RtToken *)NXZoneRealloc([self zone], tokens, (newN * sizeof(RtToken)));
         parms = (RtPointer *)NXZoneRealloc([self zone], parms, (newN * sizeof(RtPointer)));
         archiveVector = (char **)NXZoneRealloc([self zone], archiveVector, (newN * sizeof(archiveVector)));
         printfNVector = (int *)NXZoneRealloc([self zone], printfNVector, (newN * sizeof(int)));
         printfTypeVector = (int *)NXZoneRealloc([self zone], printfTypeVector, (newN * sizeof(int)));
      }
   }
   else
   {  tokens = (RtToken *)NXZoneMalloc([self zone], newN * sizeof(RtToken));
      parms = (RtPointer *)NXZoneMalloc([self zone], newN * sizeof(RtPointer));
      archiveVector = (char **)NXZoneMalloc([self zone], newN * sizeof(archiveVector));
      printfNVector = (int *)NXZoneMalloc([self zone], newN * sizeof(int));
      printfTypeVector = (int *)NXZoneMalloc([self zone], newN * sizeof(int));
   }
   n = newN;
   for (i = 0; i < n; i++)
   {  tokens[i] = newTokens[i];
      parms[i] = newParms[i];
      archiveVector[i] = newArchiveVector[i];
      printfNVector[i] = newPrintfNVector[i];
      printfTypeVector[i] = newPrintfTypeVector[i];
   }

   return ;
}

- (int)n { return n; }
- (RtToken *)tokens { return tokens; }
- (RtPointer *)parms { return parms; }
- (char **)archiveVector { return archiveVector; }
- (int *)printfNVector { return printfNVector; }
- (int *)printfTypeVector { return printfTypeVector; }

// should make this lerpable, but later...
- (BOOL)isLerpable { return YES; }

// this should only be called internally
// the receiving object needs to copy all the parameters using the passed in zone
// the assumption is that 
- _mallocPointersForN:(int)newN usingZone:(NXZone *)newZone
{
   n = newN;
   if (n)
   {  tokens = (RtToken *)NXZoneMalloc([self zone], n * sizeof(RtToken));
      if (!tokens)
      {  return nil;
      }
      parms = (RtPointer *)NXZoneMalloc([self zone], n * sizeof(RtPointer));
      if (!parms)
      {  NXZoneFree(newZone, tokens);
         return nil;
      }
      archiveVector = (char **)NXZoneMalloc([self zone], n * sizeof(char *));
      if (!archiveVector)
      {  NXZoneFree(newZone, tokens);
         NXZoneFree(newZone, parms);
         return nil;
      }
      printfNVector = (int *)NXZoneMalloc([self zone], n * sizeof(int));
      if (!printfNVector)
      {  NXZoneFree(newZone, tokens);
         NXZoneFree(newZone, parms);
         NXZoneFree(newZone, archiveVector);
         return nil;
      }
      printfTypeVector = (int *)NXZoneMalloc([self zone], n * sizeof(int));
      if (!printfTypeVector)
      {  NXZoneFree(newZone, tokens);
         NXZoneFree(newZone, parms);
         NXZoneFree(newZone, archiveVector);
         NXZoneFree(newZone, printfNVector);
         return nil;
      }
   }
   else
   {  // n == 0
      tokens = (RtToken *)NULL;
      parms = (RtPointer *)NULL;
      archiveVector = (char **)NULL;
      printfNVector = (int *)NULL;
      printfTypeVector = (int *)NULL;
   }
   return self;
}

- _setToken:(char *)newToken parm:(RtPointer)newParm i:(int)i archiveInfo:(char *)archiveInfo type:(int)parmType count:(int)howMany usingZone:(NXZone *)newZone
{
   int  j;


   tokens[i] = (RtToken)NXZoneMalloc(newZone, (1 + strlen(newToken)));
   if (!tokens[i])
   {  return nil;
   }
   strcpy(tokens[i], newToken);

   archiveVector[i] = (char *)NXZoneMalloc(newZone, (1 + strlen(archiveInfo)));
   if (!archiveVector[i])
   {  NXZoneFree(newZone, tokens[i]);
      return nil;
   }
   strcpy(archiveVector[i], archiveInfo);

   printfTypeVector[i] = parmType;
   printfNVector[i] = howMany;

   // in a perfect world, I could just copy that chunk o memory over,
   // but I'm worried about alignment problems, so I'll do it the slow way...
   switch (printfTypeVector[i])
   {  case WW_FLOAT:  parms[i] = (RtFloat *)NXZoneMalloc(newZone, sizeof(RtFloat) * howMany); 
                      for (j = 0; j < printfNVector[i]; j++)
		      {  *((RtFloat *)parms[i] + j) = *((RtFloat *)(newParm) + j);
		      }
                      break;
      case WW_COLOR:  parms[i] = (RtFloat *)NXZoneMalloc(newZone, sizeof(RtFloat) * howMany); 
                      for (j = 0; j < printfNVector[i]; j++)
		      {  *((RtFloat *)parms[i] + j) = *((RtFloat *)(newParm) + j);
		      }
                      break;
      case WW_POINT:  parms[i] = (RtFloat *)NXZoneMalloc(newZone, sizeof(RtFloat) * howMany); 
                      for (j = 0; j < printfNVector[i]; j++)
		      {  *((RtFloat *)parms[i] + j) = *((RtFloat *)(newParm) + j);
		      }
                      break;
      case WW_INT:    parms[i] = (int *)NXZoneMalloc(newZone, sizeof(int) * howMany); 
                      for (j = 0; j < printfNVector[i]; j++)
		      {  *((int *)parms[i] + j) = *((int *)(newParm) + j);
		      }
                      break;
      // hmm, strings are weirder.  
      case WW_STRING: parms[i] = (char **)NXZoneMalloc(newZone, sizeof(char *) * howMany); 
                      for (j = 0; j < printfNVector[i]; j++)
		      {  // need to additionally malloc up some memory here, folks
                         *((char **)parms[i] + j) = (char *)NXZoneMalloc(newZone, (1 + strlen(*((char **)(newParm) + j))));
                         strcpy(*((char **)parms[i] + j), *((char **)(newParm) + j));
		      }
                      break;
   }	  
   return self;
}


 - copyFromZone:(NXZone *)newZone
{
  int  i;
  //id   newCopy = [super copyFromZone:newZone];
  id   newCopy = [(WW3DShader *)[[WW3DShader alloc] init] setShader:[self shader]];


  // now we need to grovel over the parameters and copy them 
  // this is a pain, but boy, will it be cool...
  // okay, malloc up the base list of pointers
  [newCopy _mallocPointersForN:n usingZone:newZone];
  [newCopy setToken:[self token]];

  for (i = 0; i < n; i++)
  {  if (![newCopy _setToken:tokens[i] parm:parms[i] i:i 
                       archiveInfo:archiveVector[i] type:printfTypeVector[i] count:printfNVector[i] 
                       usingZone:newZone])
     {  [newCopy free];
        return nil;
     }
  }
  
  return newCopy;
}

- _lerpParametersWith:b by:(float)u
{
  int        i, j;
  RtToken    *tokensB;
  char       **archiveVectorB;
  int        *printfNVectorB;
  int        *printfTypeVectorB;
  RtPointer  *parmsB;


  // okay, first thing to realize is that this instance already has its 
  // parameters set to the (u ==0) case here.  Therefore, when in doubt, 
  // you can leave it alone and you're fine.  We now want to grovel 
  // over the two objects' parameters.  
  if (n != [b n])
  { // hmm.  looks like trouble.  At some point, we'll have to bail,
    // since the parameter lists aren't the same length.  we could
    // bail right away, since we know that "self"'s parameters are set
    // to something reasonable...  Yea, let's bail.
    return self;
  }

  tokensB = [b tokens];
  archiveVectorB = [b archiveVector];
  printfNVectorB = [b printfNVector];
  printfTypeVectorB = [b printfTypeVector];
  parmsB = [b parms];

  // okay, at this point, we know that these two objects are the same 
  // class, and the length of their parameter lists are the same. Of
  // course, this might be just a lucky coincidence, and the actual 
  // parameters might be different types.  

  for (i = 0; i < n; i++)
  {  if ((tokens[i] == tokensB[i]) || (!strcmp(tokens[i], tokensB[i])))
     {  // great! they're the same token.
        // now we need to make sure they're the same type and length of data
        // if not, bail to the next parameter
        if ((printfNVector[i] == printfNVectorB[i]) && (printfTypeVector[i] == printfTypeVectorB[i]))
	{  // okay! we're golden. Let's do a lerp...
           switch (printfTypeVector[i])
           {  case WW_FLOAT:   for (j = 0; j < printfNVector[i]; j++)
		               {  *((RtFloat *)parms[i] + j) += ((*((RtFloat *)(parmsB[i]) + j) - *((RtFloat *)parms[i] + j)) * u);
		               }
                               break;
              case WW_COLOR:   for (j = 0; j < printfNVector[i]; j++)
		               {  *((RtFloat *)parms[i] + j) += ((*((RtFloat *)(parmsB[i]) + j) - *((RtFloat *)parms[i] + j)) * u);
		               }
                               break;
              case WW_POINT:   for (j = 0; j < printfNVector[i]; j++)
		               {  *((RtFloat *)parms[i] + j) += ((*((RtFloat *)(parmsB[i]) + j) - *((RtFloat *)parms[i] + j)) * u);
		               }
                               break;
              case WW_INT:     for (j = 0; j < printfNVector[i]; j++)
		               {  *((int *)parms[i] + j) += ((*((int *)(parmsB[i]) + j) - *((int *)parms[i] + j)) * u);
			       }
                               break;
              case WW_STRING:  // can't lerp strings without more info...
                               // maybe could encode a way to do it with enumerated values, but for now, bail...
                               break;
           }
        }
        else
        {  // okay, that parameter can't be lerp'ed; on to the next
        }
     }
     else
     {  // okay, that parameter can't be lerp'ed; on to the next
     }
  }

  // all right, we've lerped our parameters, now we need to "install" them in the shader...
  for (i = 0; i < n; i++)
  {  if (![self setShaderArgNamed:tokens[i] withValue:parms[i]])
     {  NXLogError("in shader %s, <%s> is not a valid arg.\n", 
		   [self shader], tokens[i]);
     }
  }

  return self;
}


// note: because we've made the WWSampleList "safe" for having
// multiple samples with the same data, it's perfectly valid to return
// yourself or b
- lerpWith:b by:(float)uValue
{
   id  newMe = nil;


   if (([self class] != [b class]) || (uValue <= 0.0) || (![self shader]) || (![b shader]) || (strcmp([self shader], [b shader]))) 
   {  return self;
   }

   if (uValue >= 1.0)
   {  return b;
   }

   newMe = [self copyFromZone:[self zone]];

   // okay, now we have to get newMe to linearly interpolate it
   // parameters partway between "self" and "b"
   [newMe _lerpParametersWith:b by:uValue];

   return newMe;
}

- lerpSelfWith:b by:(float)uValue
{
   if (([self class] != [b class]) || (uValue <= 0.0) || (![self shader]) || (![b shader]) || (strcmp([self shader], [b shader])))
   {  return self;
   }

   if (uValue >= 1.0)
   {  return b;
   }

   // okay, now we have to get newMe to linearly interpolate it
   // parameters partway between "self" and "b"
   [self _lerpParametersWith:b by:uValue];

   return self;
}


// in 3.4, shaders aren't motion blurrable
- (BOOL)isMotionBlurrable { return NO; }
- (BOOL)isCompoundCommand { return NO; }

// stuff for 3DReality, as well as general UI expansion

- setIconImage:image  {  iconImage = image; return self; }

- setIconImageNamed:(const char *)name
{  // It's actually a bad idea to free this image, because it might be shared...
   if (name && *name)
   { iconImage = [NXImage findImageNamed:name];
     if (!iconImage) 
     {  iconImage = [[NXImage alloc] init];
        // [iconImage setDataRetained:YES];
        if (![iconImage loadFromFile:name])
        {  NXLogError("unable to load image from file <%s>\n", name);
           return nil; 
 	}
     }	
   }
   return self;
}

- iconImage { return iconImage; }


// target/action messages
- takeColorArg:sender
{
  [self setShaderArg:[sender shaderArgName] colorValue:[sender color]];
  return self;
} 

- takeFloatArg:sender
{
  [self setShaderArg:[sender shaderArgName] floatValue:[sender floatValue]];
  return self;
} 

- takePointArg:sender
{
  RtPoint  aPoint;

  N3D_XComp(aPoint) = [[sender cellAt:0 :0] floatValue];
  N3D_YComp(aPoint) = [[sender cellAt:0 :1] floatValue];
  N3D_ZComp(aPoint) = [[sender cellAt:0 :2] floatValue];
  [self setShaderArg:[sender shaderArgName] pointValue:aPoint];
  return self;
} 

- takeStringArg:sender
{
  [self setShaderArg:[sender shaderArgName] stringValue:[sender stringValue]];
  return self;
} 


// WavesWorld archiving:
// writeEve:(NXStream *)stream
// writeScene:(NXStream *)stream

// this is a routine for subclasses to call when archiving themselves to an eve file or a scene...
- writeParameterList:(NXStream *)stream
{
  int  i, j;

  
   if (!n)
   {  NXPrintf(stream, ";");
      return nil;
   }
   for (i = 0; i < n; i++)
   {  NXPrintf(stream, "{%s} ", tokens[i]);
      NXPrintf(stream, "{ ");
      switch (printfTypeVector[i])
      {  case WW_FLOAT:   for (j = 0; j < printfNVector[i]; j++)
	                  {  NXPrintf(stream, "%f ", *((RtFloat *)(parms[i]) + j));
			  }
                          break;
         case WW_COLOR:   for (j = 0; j < printfNVector[i]; j++)
 	                  {  NXPrintf(stream, "%f ", *((RtFloat *)(parms[i]) + j));
			  }
                          break;
         case WW_POINT:   for (j = 0; j < printfNVector[i]; j++)
 	                  {  NXPrintf(stream, "%f ", *((RtFloat *)(parms[i]) + j));
			  }
                          break;
         case WW_INT:     for (j = 0; j < printfNVector[i]; j++)
 	                  {  NXPrintf(stream, "%d ", *((int *)(parms[i]) + j));
			  }
                          break;
         case WW_STRING:  for (j = 0; j < printfNVector[i]; j++)
 	                  {  NXPrintf(stream, "{%s} ", *((char **)(parms[i]) + j));
			  }
                          break;
      }	  
      NXPrintf(stream, "} ");
   }
   NXPrintf(stream, ";");
   return self;
}

- writeEve:(NXStream *)stream atTabLevel:(int)tab
{
   int  i;


   for (i = 0; i < tab; i++)
   {  NXPrintf(stream, "\t");
   }

   switch ([self shaderType])
   {  
     case SLO_TYPE_SURFACE:
       NXPrintf(stream, "Surface %s ", shader); 
       break;

     case SLO_TYPE_LIGHT:
       if (!lightHandle)
       {  lightHandleSeed++;
          lightHandle = (char *)malloc(64);
          sprintf(lightHandle, "%d", lightHandleSeed);
       }
       if (pointOrArea)
       {  NXPrintf(stream, "AreaLightSource %s %s ", shader, lightHandle); 
       }
       else
       {  NXPrintf(stream, "LightSource %s %s ", shader, lightHandle); 
       }
       break;

     case SLO_TYPE_DISPLACEMENT:
       NXPrintf(stream, "Displacement %s ", shader); 
       break;

     case SLO_TYPE_VOLUME:
       switch (volumeType)
       {  case WW_ATMOSPHERE:  NXPrintf(stream, "Atmosphere %s ", shader); 
                               break;
          case WW_INTERIOR:    NXPrintf(stream, "Interior %s ", shader); 
                               break;
          case WW_EXTERIOR:    NXPrintf(stream, "Exterior %s ", shader); 
                               break;
          default:             NXLogError("# unknown volume shader %s ", (char *)[self shader]);
       }
       
       break;

     case SLO_TYPE_TRANSFORMATION:
       NXPrintf(stream, "Deformation %s ", shader); 
       break;

     case SLO_TYPE_IMAGER:
       NXPrintf(stream, "Imager %s ", shader); 
       break;

     default:
       NXPrintf(stream, "# unknown shader %s ", shader); 
       return nil;
   }
  
   [self writeParameterList:stream];

   return self;
}

- writeScene:(NXStream *)stream atTabLevel:(int)tab
{
   return [self writeEve:stream atTabLevel:tab];
}

- write3DTextScene:(NXStream *)stream atTabLevel:(int)tab index:(int)index time:(float)time until:(float)lastTime
{
   int  i;


   for (i = 0; i < tab; i++)
   {  NXPrintf(stream, "\t");
   }

   NXPrintf(stream, "startShape %s; ", [[self class] name]);
   // need tab
   // need index (position in current list)
   NXPrintf(stream, 
	    "EveCmd {Translate [expr { %d * $__text__(tabLength)}] [expr {$__text__(spacingFactor) * %d * $__text__(spacing) * $__text__(fontSize)}] 0 };\n", 
            tab, index); 
   NXPrintf(stream, "  EveCmd {WW3DText $__text__(fontName) $__text__(fontSize) {");
   [self writeEve:stream atTabLevel:tab];
   NXPrintf(stream, "} left;}\n");
  NXPrintf(stream, "endShape;\n");

  return self;
}


- writeInventorAtTime:(float)currentTime to:(NXStream *)stream atTabLevel:(int)tab
{
   int  i;


  for (i = 0; i < tab; i++)
   {  NXPrintf(stream, "\t");
   }
   NXPrintf(stream, "# WW3DShader %s called;", [self shader]); 
   return self;

}

// NeXTSTEP archiving:

#define typeVectorVersion1 "i"
#define typeValuesVersion1 &n

#define typeVector "i@"
#define typeValues &n, &iconImage

- read:(NXTypedStream*)stream 
{
  int version, i;


  [super read:stream];

  version = NXTypedStreamClassVersion(stream,"WW3DShader");
  if (version == 0) NXReadTypes(stream,"i",&version), version=1;
  if (version == 1)
  {  NXReadTypes(stream, typeVectorVersion1, typeValuesVersion1);
     tokens = (char **)NXZoneMalloc([self zone], (sizeof(char *) * n));
     archiveVector = (char **)NXZoneMalloc([self zone], (sizeof(char *) * n));
     parms = (RtPointer *)NXZoneMalloc([self zone], (sizeof(char *) * n));
     for (i = 0; i < n; i++)
     {  NXReadTypes(stream, "**", &(tokens[i]), &(archiveVector[i]));
	NXReadTypes(stream, archiveVector[i], &(parms[i]));
     }
  } 
  if (version == 2)
  {  NXReadTypes(stream, typeVector, typeValues);
     tokens = (char **)NXZoneMalloc([self zone], (sizeof(char *) * n));
     archiveVector = (char **)NXZoneMalloc([self zone], (sizeof(char *) * n));
     parms = (RtPointer *)NXZoneMalloc([self zone], (sizeof(char *) * n));
     for (i = 0; i < n; i++)
     {  NXReadTypes(stream, "**", &(tokens[i]), &(archiveVector[i]));
	NXReadTypes(stream, archiveVector[i], &(parms[i]));
     }
  } 
  if (version == 3)
  {  NXReadTypes(stream, typeVector, typeValues);
     tokens = (char **)NXZoneMalloc([self zone], (sizeof(char *) * n));
     archiveVector = (char **)NXZoneMalloc([self zone], (sizeof(char *) * n));
     parms = (RtPointer *)NXZoneMalloc([self zone], (sizeof(char *) * n));
     for (i = 0; i < n; i++)
     {  NXReadTypes(stream, "**", &(tokens[i]), &(archiveVector[i]));
	NXReadTypes(stream, archiveVector[i], &(parms[i]));
     }
     NXReadTypes(stream, "*ic", &quickShader, &lightHandle, &usingSupportedQRManShader);
     NXReadArray(stream, "f", 3, ribColor);
  } 
  if (version == 4)
  {  NXReadTypes(stream, typeVector, typeValues);
     tokens = (char **)NXZoneMalloc([self zone], (sizeof(char *) * n));
     archiveVector = (char **)NXZoneMalloc([self zone], (sizeof(char *) * n));
     parms = (RtPointer *)NXZoneMalloc([self zone], (sizeof(char *) * n));
     for (i = 0; i < n; i++)
     {  NXReadTypes(stream, "**", &(tokens[i]), &(archiveVector[i]));
	NXReadTypes(stream, archiveVector[i], &(parms[i]));
     }
     NXReadTypes(stream, "*ic", &quickShader, &lightHandle, &usingSupportedQRManShader);
     NXReadArray(stream, "f", 3, ribColor);
     NXReadTypes(stream, "ic", &volumeType, &pointOrArea);
  } 

  return self; 
}

- write:(NXTypedStream*)stream 
{
  int  i;

    
  [super write:stream];

  NXWriteTypes(stream,typeVector, typeValues);
  for (i = 0; i < n; i++)
  {  NXWriteTypes(stream, "**", &(tokens[i]), &(archiveVector[i]));
     NXWriteTypes(stream, archiveVector[i], &(parms[i]));
  }
  NXWriteTypes(stream, "*ic", &quickShader, &lightHandle, &usingSupportedQRManShader);
  NXWriteArray(stream, "f", 3, ribColor);
  NXWriteTypes(stream, "ic", &volumeType, &pointOrArea);

  return self;
}

// boy, this is dumb... This is to get around the stupid warnings from the compiler - ask wave for details
- class { return [super class]; }

- (float)lastSampleIsAt { return 0.0; }

- (BOOL)isMoot
{
  return NO;
}

- (BOOL)isMootStartingAt:(float)startTime endingAt:(float)endTime  {  return [self isMoot];  }

- (BOOL)theSameAs:otherRIBCommand
{
  return NO;
}

- (BOOL)similarTo:otherRIBCommand 
{
  if ([self class] != [otherRIBCommand class])
  {  return NO;
  }
  return YES;
}


//WAVE FIX ME!!!
- (unsigned long int)maxSampleBandwidth { return 100; }

@end
